from google.appengine.ext import db
from google.appengine.ext.db import polymodel

import random
import itertools
import sys
import logging

from models.gamebase import *
from models.gamelog  import *
'''
Helper Objects
'''
   
class BuildingTiles(object):
    buildings=( 
         {"name":"market","level":1,"action":"d"}, 
         {"name":"shipyard","level":1,"action":"s","bonus":"c"}, 
         {"name":"workshop","level":1,"bonus":"ii"}, 
         {"name":"barracks","level":2,"action":"a"}, 
         {"name":"guildhall","level":2,"action":"s/d"}, 
         {"name":"bank","level":2,"bonus":"ff"}, 
         {"name":"docks","level":3,"action":"o+s"}, 
         {"name":"fortress","level":3,"action":"o/a","bonus":"p"}, 
         {"name":"theatre","level":3,"bonus":"cc"}, 
         {"name":"cartographer","level":4,"action":"s+s"}, 
         {"name":"tradeoffice","level":4,"action": "d+d"}, 
         {"name":"university","level":4,"bonus":"ggg"}, 
         {"name":"exchange","level":5,"action":"p", "bonus":"ff"}, 
         {"name":"museum","level":5,"action":"p","bonus":"cc"}, 
         {"name":"parliament","level":5,"action":"p","bonus":"pp"},
         {"name":"colonialhouse","level":0,"action":"occ"}
    )
     
    @classmethod
    def initial_setup(cls):
        return [6-b["level"] for b in cls.buildings if b["level"] > 0]

    @classmethod
    def tile_names(cls):
        return [b["name"] for b in cls.buildings]
    
    @classmethod 
    def bonus(cls, building):
        for b in cls.buildings:
            if b["name"]==building and "bonus" in b.keys():
                return b["bonus"]
        return ""

    @classmethod 
    def building_glory(cls, building):
        return len([c for c in cls.bonus(building) if c=="g"])

    @classmethod 
    def action(cls, building):
        for b in cls.buildings:
            if b["name"]==building and "action" in b.keys():
                return b["action"]
        return ""

    @classmethod
    def level(cls, building):
        return cls.buildings[cls.index(building)]["level"]
    
    @classmethod
    def index(cls, building):
        return [b["name"] for b in cls.buildings].index(building)
    
class CardDeck():
    _cards= {"eu": ('cf', 'ccp', 'ppp','icfp','ggggg','cccggg' ), 
            "sl": ('ii', 'ff', 'ffi', 'iii','iiff','iiiff' ), 
            "fe": ('iicg', 'if', 'iif','iiff','icfpg','pppggg' ), 
            "in": ('cfpg', 'cp', 'cfp','cpff','ffppg','ffggpp' ),
            "na": ('ppfg', 'ii', 'fff','iicc','ccppg','gggggg' ),
            "ca": ('ccpg', 'pp', 'ppf','ffff','pppgg','iiiggg' ),
            "sa": ('pppg', 'cc', 'ccc','cccc','fffgg','fffggg' ),            
            "af": ('ffpg', 'ip', 'ifp','ffpp','iifpg','iiffgg' ),
            }
        
    @staticmethod
    def pile(pile_id):
        return [pile_id+str(i) for i in range(6)]
   
    @classmethod
    def card_value(cls, card_id):
        if card_id:
            return cls._cards[card_id[0:2]][int(card_id[2])]
        else:
            return ""

    @classmethod
    def card_glory(cls, card_id):
        return len([c for c in cls.card_value(card_id) if c=="g"])

    @classmethod
    def card_level(cls, card_id):
        return int(card_id[2])

    @classmethod
    def card_deck(cls, card_id):
        return card_id[0:2]

    @classmethod
    def card_region(cls, card_id):
        return "eu" if CardDeck.card_deck(card_id)=="sl" else CardDeck.card_deck(card_id)

    @classmethod
    def is_govenor_card(cls, card_id):
        return CardDeck.card_level(card_id)==0 and CardDeck.card_deck(card_id) != "eu" and CardDeck.card_deck(card_id) != "sl"

    @classmethod
    def is_slavery_card(cls, card_id):
        return CardDeck.card_deck(card_id) == "sl"
            
    
class TokenBag():
    score_tokens=["i", "f", "c", "p"] 
    action_tokens=["o/d", "s/d", "pay", "att"]
       
    def __init__(self):
        self._tokens={"p":25,"c":20,"f":17,"i":17,"o/d":6,"s/d":6,"pay":2,"att":2}

    def __iter__(self):
        return self.item()
    
    def next(self, count=1):
        if (count==1):
            return self.item().next()
        else:
            return list(itertools.islice(self, count))
           
    def item(self):
        while len(self._tokens) > 0:
            token=random.choice(self._tokens.keys())
            self._tokens[token]-=1
            if (self._tokens[token]==0):
                self._tokens.pop(token)
            yield token
                
    @staticmethod       
    def actions(token):
        if token=="o/d":
            return ["occupy", "draw"]
        elif token=="s/d":
            return ["ship", "draw"]
        elif token=="pay":
            return ["pay"]
        elif token=="att":
            return "attack"
        else:
            return []
        
class ScoreCalculator(object):    
    def __init__(self, game, player):
        self.position=player.position
        pb=player.playerboard
        self.industry=self.track_points(pb.industry)
        self.culture=self.track_points(pb.culture)
        self.finance=self.track_points(pb.finance)
        self.politics=self.track_points(pb.politics)
        self.occupied_cities=sum([c.point_value if c.occupier==player else 0 for c in game.boardstate.cities ])
        self.controlled_connections=len([c for c in game.boardstate.connections if c.occupier==player])
        self.card_glory=sum([CardDeck.card_glory(c) for c in pb.cards])
        self.free_govenor=3 if pb.card_count()<pb.card_limit or (pb.card_count()==pb.card_limit and pb.has_governor_card==False) else 0
        self.building_glory=sum(BuildingTiles.building_glory(b) for b in pb.buildings)
        self.extra_population=pb.harbour_population/3
        self.slavery=len(pb.discarded_slavery_cards)*-1
        self.total=self.position+self.industry+self.culture+self.finance+self.politics+self.occupied_cities+ \
            self.controlled_connections+self.card_glory+self.free_govenor+self.building_glory+self.extra_population+self.slavery

    def track_points(self,score):
        return 0 if score < 2 else 2 if score <4 else 4 if score < 7 else 7 if score < 10 else score   
    
    def __repr__(self):
        return "Industry: %d,Culture: %d, Finance: %d, Politics: %d, Cities: %d, Connections: %d, Cards: %d, " \
            "Free Govenor: %d, Buildings: %d, Population: %d, Slavery: %d == Total: %d ==" % \
            (self.industry,self.culture,self.finance,self.politics,self.occupied_cities,self.controlled_connections,
             self.card_glory,self.free_govenor,self.building_glory,self.extra_population,self.slavery,self.total)
           
class GameState(GameModel):
    '''
    This  object contains all the information about an individual game..e.g. players, game state, board
    '''
    _phases=("build","growth","salary","action","discard","gameover")
    _child_collections=("players", "boardstate_set", "buildingstock_set", "player_scores")
    name = db.StringProperty(required=True)
    password = db.StringProperty()
    round = db.IntegerProperty(required=True, default=0)
    phase = db.StringProperty(name="phase",choices=set(_phases))
    program_counter = db.IntegerProperty(default=0, required=True)
    random_seed = db.IntegerProperty()
    
    def start_player(self):
        return self.players.filter("is_start_player =", True).get()

    def set_start_player(self, player):
        p=self.start_player
        if p:
            p.is_start_player=False
            p.put()        
        player.is_start_player=True
        player.put()
        
    start_player=property(start_player, set_start_player)
        
    def current_player(self):
        return self.players.filter("is_current_player =", True).get()

    def set_current_player(self, player):
        p=self.current_player
        if p:
            p.is_current_player=False
            p.put()
        if player:
            player.is_current_player=True
            player.put()
        
    current_player=property(current_player, set_current_player)
    
    @property
    def next_player(self):
        players=self.ordered_player_list(start_with_player=self.current_player)
        next_players=[p for p in players[1:] if not p.has_passed]
        return next_players[0] if len(next_players) > 0 else None
  
    @property
    def next_start_player(self):
        players=self.ordered_player_list(start_with_player=self.start_player)
        return players[1]

    def randomise_start_player(self):
        player=random.choice(list(self.players)) 
        self.start_player=player
    
    @property 
    def boardstate(self):
        return self.boardstate_set.get()

    @property 
    def buildingstock(self):
        return self.buildingstock_set.get()
    
    def ordered_player_list(self, start_with_player=None):
        players=list(self.players)
        start_player=start_with_player or [p for p in players if p.is_start_player][0] 
        return sorted(players, key=lambda x:x.position + (len(players) if x.position < start_player.position else 0))
    
    def reset(self, random_seed=None):
        self.round=0
        self.program_counter=0
        if random_seed==None:
            random.seed()
            random_seed=random.randint(-2147483648,2147483647)
        random.seed(random_seed)
        self.random_seed=random_seed
    
        #Reset the Board, If it doesn't exists then create it first
        board=self.boardstate
        if not board:            
            board=BoardState(parent=self, game=self)
            board.put()
        board.reset()
        
        #Reset the Building Stock, If it doesn't exists then create it first
        buildings=self.buildingstock
        if not buildings:
            buildings=BuildingStock(parent=self, game=self)
            buildings.put()
        buildings.reset()
        self.put()
        
    def start_game(self):
        self.phase=GameState._phases[0]
        if not self.boardstate:
            self.reset()
        if not self.start_player:
            self.randomise_start_player()
        self.round=1
        self.current_player=self.start_player
        self.put()
                          
class Player(GameModel):
    _child_collections=("playerboard_set",)
    _colours=("red","white","green","purple","black")
    position = db.IntegerProperty(required=True, choices=set([1,2,3,4,5]))
    game= db.ReferenceProperty(GameState, required=True, collection_name="players")
    name = db.StringProperty(required=True)
    user = db.UserProperty()
    colour = db.StringProperty(choices=set(_colours))
    is_ready=db.BooleanProperty(default=False)
    is_owner = db.BooleanProperty(default=False)
    is_current_player = db.BooleanProperty(default=False)
    is_start_player = db.BooleanProperty(default=False)
    has_passed = db.BooleanProperty(default=False)
    
    @property 
    def playerboard(self):
        return self.playerboard_set.get()
    
    @property
    def card_limit_exceeded(self):
        return self.playerboard.card_limit_exceeded;        
        
class PlayerBoard(GameModel):
    player=db.ReferenceProperty(Player, required=True)
    score_tokens=db.StringProperty(default="")
    buildings=db.StringListProperty(required=True, default=["colonialhouse"],validator=lambda value:len(value)<=8 and value in BuildingTiles.tile_names())
    buildings_occupied=db.ListProperty(bool, required=True, default=[False],validator=lambda value:len(value)<=8)
    harbour_population=db.IntegerProperty(required=True, default=0)
    harbour_tokens=db.StringListProperty(required=True, default=[])
    cards=db.StringListProperty(required=True, default=[])
    discarded_slavery_cards=db.StringListProperty(default=[])
    
    @property
    def industry(self):
        return self._get_score("i")

    @property
    def culture(self):
        return self._get_score("c")

    @property
    def finance(self):
        return self._get_score("f")

    @property
    def politics(self):
        return self._get_score("p")

    @property
    def glory(self):
        return self._get_score("g")

    @property
    def build_level(self):
        return self._get_level(self._get_score("i"))

    @property
    def growth_level(self):
        return self._get_level(self._get_score("c"))

    @property
    def salary_level(self):
        return self._get_level(self._get_score("f"))

    @property
    def card_limit(self):
        return self._get_level(self._get_score("p"))

    @property
    def card_limit_exceeded(self):
        return self.card_count() > self.card_limit
    
    @property
    def has_governor_card(self):
        return any(CardDeck.is_govenor_card(c) for c in self.cards)
                   
    def card_count(self):
        # Allow for 1 free governor card
        cardlen = len(self.cards) - len(set([1 for c in self.cards if CardDeck.is_govenor_card(c)]))
        #Allow for 1 free slavery if we have less than 5 other cards
        return cardlen - len(set([1 for c in self.cards if CardDeck.card_deck(c)=="sl" ])) if cardlen < 5 else cardlen
    
    def _get_score(self, category):
        score=self.score_tokens.count(category)
        for c in self.cards:
            score+=CardDeck.card_value(c).count(category) if c else 0
        for b in self.buildings:
            score+=BuildingTiles.bonus(b).count(category)
        return score if score < 15 else 15

    def _get_level(self, score):
        return 1 if score < 2 else 2 if score <4 else 3 if score < 7 else 4 if score < 10 else 5   
            
    def add_token(self, token):
        if token in TokenBag.score_tokens:
            self.score_tokens+=token
        elif token in TokenBag.action_tokens:
            self.harbour_tokens.append(token)
        else:
            raise GameDataException(-35001, "Invalid Token - %s" % token)

    def remove_token(self, token):
        if token in TokenBag.action_tokens and token in self.harbour_tokens:
            token_index=self.harbour_tokens.index(token)
            self.harbour_tokens.pop(token_index)
            
        else:
            raise GameDataException(-35001, "Invalid Token - %s" % token)
    
    def add_building(self, building):
        self.buildings.append(building)
        self.buildings_occupied.append(False)

    def add_card(self, card):
        self.cards.append(card)
        
    def next_unoccupied_building_index(self, building):
        unoccupied_buildings=[i for i,b in enumerate(self.buildings) if b==building and self.buildings_occupied[i]==False]
        return unoccupied_buildings[0] if len(unoccupied_buildings)>0 else None
    
class BoardState(GameModel):
    _child_collections=("regions", "connections", "cities")
    game=db.ReferenceProperty(GameState, required=True)
    discarded_governor_cards=db.StringListProperty(default=[])
    slavery_abolished = db.BooleanProperty(default=False)

    def get_region_by_id(self, region_id):
        return [r for r in self.regions if r.region_id==region_id][0]
    
    def reset(self):
        token_bag=TokenBag()
        self._delete_children(self.regions)
        self._delete_children(self.connections)
        self.put()
        
        # Set up Europe & the Med
        card_pile=CardPile(cards=CardDeck.pile("eu")).put()
        slavery_card_pile=CardPile(cards=CardDeck.pile("sl")).put()
        europe=Region(parent=self, boardstate=self, number=0, name="Europe & the Mediterranean", region_id="eu", card_pile=card_pile, slavery_card_pile=slavery_card_pile).put()

        # Set up Far East
        shipping_track=ShippingTrack(length=8).reset(token_bag).put()
        card_pile=CardPile(cards=CardDeck.pile("fe")).put()
        fareast=Region(parent=self,boardstate=self, number=1, name="The Far East", region_id="fe", shipping_track=shipping_track, card_pile=card_pile).put()

        #Set Up India
        card_pile=CardPile(cards=CardDeck.pile("in")).put()
        shipping_track=ShippingTrack(length=7).reset(token_bag).put()
        india=Region(parent=self,boardstate=self, number=2, name="India",region_id="fe", shipping_track=shipping_track, card_pile=card_pile).put()

        #Set Up North America
        card_pile=CardPile(cards=CardDeck.pile("na")).put()
        shipping_track=ShippingTrack(length=7).reset(token_bag).put()
        north_america=Region(parent=self,boardstate=self, number=3, name="North America", region_id="na",shipping_track=shipping_track, card_pile=card_pile).put()

        #Set Up Caribbean
        card_pile=CardPile(cards=CardDeck.pile("ca")).put()
        shipping_track=ShippingTrack(length=6).reset(token_bag).put()
        caribbean=Region(parent=self,boardstate=self, number=4, name="Caribbean",region_id="ca",shipping_track=shipping_track, card_pile=card_pile).put()

        #Set Up South America
        card_pile=CardPile(cards=CardDeck.pile("sa")).put()
        shipping_track=ShippingTrack(length=6).reset(token_bag).put()
        south_america=Region(parent=self,boardstate=self, number=5, name="South America", region_id="sa",shipping_track=shipping_track, card_pile=card_pile).put()

        #Set Up South America
        card_pile=CardPile(cards=CardDeck.pile("af")).put()
        shipping_track=ShippingTrack(length=6).reset(token_bag).put()
        africa=Region(parent=self,boardstate=self, number=6, name="Africa",region_id="af", shipping_track=shipping_track, card_pile=card_pile).put()
        
        # Populate Cities
        paris=City(parent=self,boardstate=self, region=europe, number=1,name="Paris", trade_token=token_bag.next()).put()
        venice=City(parent=self,boardstate=self, region=europe, number=2, name="Venice",trade_token=token_bag.next()).put()
        constantinople=City(parent=self,boardstate=self, region=europe, number=3, name="Constantinople",trade_token=token_bag.next()).put()
        cairo=City(parent=self,boardstate=self, region=europe, number=4, name="Cairo",trade_token=token_bag.next()).put()
        tripoli=City(parent=self,boardstate=self, region=europe, number=5, name="Tripoli",trade_token=token_bag.next()).put()
        tunis=City(parent=self,boardstate=self, region=europe, number=6, name="Tunis",trade_token=token_bag.next()).put()
        madrid=City(parent=self,boardstate=self, region=europe, number=7, name="Madrid",trade_token=token_bag.next()).put()
        lisbon=City(parent=self,boardstate=self, region=europe, number=8, name="Lisbon",trade_token=token_bag.next()).put()
        amsterdam=City(parent=self,boardstate=self, region=europe, number=9, name="Amsterdam",trade_token=token_bag.next()).put()
        london=City(parent=self,boardstate=self, region=europe, number=10, name="London",trade_token=token_bag.next()).put()

        singapore=City(parent=self,boardstate=self, region=fareast, number=11, name="Singapore",trade_token=token_bag.next()).put()
        macau=City(parent=self,boardstate=self, region=fareast, number=12, name="Macau",trade_token=token_bag.next()).put()
        peking=City(parent=self,boardstate=self, region=fareast, number=13, name="Peking",trade_token=token_bag.next()).put()
        jakarta=City(parent=self,boardstate=self, region=fareast, number=14, name="Jakarta",trade_token=token_bag.next()).put()
        osaka=City(parent=self,boardstate=self, region=fareast, number=15, name="Osaka",trade_token=token_bag.next(), point_value=2).put()

        karachi=City(parent=self,boardstate=self, region=india, number=16, name="Karachi",trade_token=token_bag.next()).put()
        madras=City(parent=self,boardstate=self, region=india, number=17, name="Madras",trade_token=token_bag.next()).put()
        calcutta=City(parent=self,boardstate=self, region=india, number=18, name="Calcutta",trade_token=token_bag.next()).put()

        new_orleans=City(parent=self,boardstate=self, region=north_america, number=19, name="New Orleans",trade_token=token_bag.next()).put()
        boston=City(parent=self,boardstate=self, region=north_america, number=20, name="Boston",trade_token=token_bag.next()).put()
        montreal=City(parent=self,boardstate=self, region=north_america, number=21, name="Montreal",trade_token=token_bag.next()).put()

        nassau=City(parent=self,boardstate=self, region=caribbean, number=22, name="Nassau",trade_token=token_bag.next()).put()
        santa_domingo=City(parent=self,boardstate=self, region=caribbean, number=23, name="Santa Domingo",trade_token=token_bag.next()).put()
        san_juan=City(parent=self,boardstate=self, region=caribbean, number=24, name="San Juan",trade_token=token_bag.next()).put()

        lima=City(parent=self,boardstate=self, region=south_america, number=25, name="Lima",trade_token=token_bag.next(),point_value=2).put()
        rio=City(parent=self,boardstate=self, region=south_america, number=26, name="Rio de Janiero",trade_token=token_bag.next()).put()
        fortaleza=City(parent=self,boardstate=self, region=south_america, number=27, name="Forataleza",trade_token=token_bag.next()).put()

        kinshasa=City(parent=self,boardstate=self, region=africa, number=28, name="Kinshasa",trade_token=token_bag.next()).put()
        dar_es_salaam=City(parent=self,boardstate=self, region=africa, number=29, name="Dar es Salaam",trade_token=token_bag.next()).put()
        
        #Populate Connections
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=paris, b_end=venice).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=venice, b_end=constantinople).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=constantinople, b_end=cairo).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=cairo, b_end=tripoli).put()

        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=london, b_end=karachi).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=karachi, b_end=madras).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=madras, b_end=amsterdam).put()

        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=amsterdam, b_end=singapore).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=singapore, b_end=calcutta).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=calcutta, b_end=macau).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=macau, b_end=constantinople).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=peking, b_end=jakarta).put()

        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=london, b_end=montreal).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=boston, b_end=montreal).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=boston, b_end=paris).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=new_orleans, b_end=tunis).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=new_orleans, b_end=nassau).put()

        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=madrid, b_end=nassau).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=san_juan, b_end=nassau).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=san_juan, b_end=lisbon).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=santa_domingo, b_end=fortaleza).put()

        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=fortaleza, b_end=lisbon).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=rio, b_end=madrid).put()

        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=kinshasa, b_end=tunis).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=kinshasa, b_end=dar_es_salaam).put()
        Connection(parent=self,boardstate=self, trade_token=token_bag.next(), a_end=dar_es_salaam, b_end=cairo).put()

        self.put()
        return self
    
class ShippingTrack(GameModel):
    length=db.IntegerProperty(required=True)
    items=db.StringListProperty()
    occupiers=db.ListProperty(db.Key) #references the Player Object Occupying the track
    
    def reset(self, token_bag):
        self.items = token_bag.next(self.length)
        self.occupiers=[]
        return self

    def take_token(self, player):
        token=self.items.pop(0) if len(self.items) > 0 else None
        self.occupiers.append(player.key())
        return token
    
    

class CardPile(GameModel):
    cards=db.StringListProperty()
    
    def take_card(self):
        return self.cards.pop(0)

    def put_card(self, card_id):
        self.cards.append(card_id)
        self.cards.sort()
    
    def next_card(self):
        return self.cards[0] if len(self.cards) > 0 else None
    
    def next_next_card(self):
        return self.cards[1] if len(self.cards) > 1 else None
        
    
class Region(GameModel):
    _child_collections=("card_pile", "slavery_card_pile", "shipping_track")
    number=db.IntegerProperty(required=True)
    boardstate=db.Reference(BoardState, required=True, collection_name="regions")
    name=db.StringProperty(required=True)
    region_id=db.StringProperty(required=True)
    shipping_track = db.Reference(ShippingTrack)
    card_pile = db.Reference(CardPile, required=True, collection_name="region_list")
    slavery_card_pile = db.Reference(CardPile, collection_name="slavery_region_list")
    #cities = collection of City objects
    
    def is_open(self, is_occupy_and_ship=False):
        return True if self.number == 0 else (len(self.shipping_track.occupiers) >= self.shipping_track.length-1) if is_occupy_and_ship else (len(self.shipping_track.occupiers) >= self.shipping_track.length)
    
    def presence(self, player):
        count=self.cities.filter("occupier =", player).count()
        if self.shipping_track:
            for o in self.shipping_track.occupiers:
                count += 1 if o==player.key() else 0
        return count 
        
class City(GameModel):
    boardstate=db.ReferenceProperty(BoardState, required=True, collection_name="cities")
    region=db.ReferenceProperty(Region, required=True, collection_name="cities")
    number=db.IntegerProperty()
    name=db.StringProperty()
    occupier=db.ReferenceProperty(Player)
    trade_token=db.StringProperty()
    point_value=db.IntegerProperty(required=True, default=1)
    #a_ends = Collection of Connection objects
    #b_ends - Collection of Connection objects
        
    def connected_cities(self):
        cities=[]
        for c in list(self.a_ends):
            cities.append(c.b_end)
        for c in list(self.b_ends):
            cities.append(c.a_end)
            
        return cities

    def connections(self):
        return list(self.a_ends)+list(self.b_ends)
    
    def take_token(self, player):
        token=self.trade_token
        self.trade_token=""
        self.occupier=player
        return token

    def attack(self, player):
        if self.occupier==None or self.occupier==player:
            raise GameDataException(-35001, "Attempt to attack city %s, but it is not occupied by another player" & self.name)
        old_occupier=self.occupier
        self.occupier=player
        return old_occupier
    
class PlayerScore(GameModel):
    game=db.ReferenceProperty(GameState, required=True, collection_name="player_scores")
    player=db.ReferenceProperty(Player, required=True)
    finish_position=db.IntegerProperty(required=True)
    industry=db.IntegerProperty(required=True)
    culture=db.IntegerProperty(required=True)
    finance=db.IntegerProperty(required=True)
    politics=db.IntegerProperty(required=True)
    occupied_cities=db.IntegerProperty(required=True)
    controlled_connections=db.IntegerProperty(required=True)
    card_glory=db.IntegerProperty(required=True)
    free_govenor=db.IntegerProperty(required=True)
    building_glory=db.IntegerProperty(required=True)
    extra_population=db.IntegerProperty(required=True)
    slavery=db.IntegerProperty(required=True)
    total=db.IntegerProperty(required=True)
    
class Connection(GameModel):
    boardstate=db.Reference(BoardState, required=True, collection_name="connections")
    occupier=db.ReferenceProperty(Player)
    trade_token=db.StringProperty()
    a_end=db.Reference(City, required=True, collection_name="a_ends")
    b_end=db.Reference(City, required=True, collection_name="b_ends")

    def take_token(self):
        token=self.trade_token
        self.trade_token=None
        return token
    
class BuildingStock(GameModel):
    game=db.ReferenceProperty(GameState, required=True)
    buildings=db.ListProperty(int)
    
    def reset(self):
        self.buildings=BuildingTiles.initial_setup()
        self.put()
        return self
    
    def building_count(self, building):
        return self.buildings[BuildingTiles.index(building)]
    
    def take_building(self, building):
        index = BuildingTiles.index(building)
        if (self.buildings[index]>0):
            self.buildings[index]-=1
        else:
            raise GameDataException(-35011, "Attempt to take building %s, but there are none left" % building)

    def minimum_level(self):
        """Returns the lowest level of the remaining buildings"""
        min_level=1
        for index, num_buildings in enumerate(self._buildings):
            min_level=BuildingTiles.buildings[index]["level"]
            if num_buildings > 0:
                break
        return min_level
            